package org.finesys.common.websocket.config;

import static org.springframework.messaging.simp.SimpMessageHeaderAccessor.SESSION_ID_HEADER;

import org.finesys.common.websocket.handler.WebSocketClientInboundChannel;
import org.finesys.common.websocket.handler.WebSocketClientOutboundChannel;
import org.finesys.common.websocket.properties.WebSocketProperties;
import org.finesys.common.websocket.support.WebSocketHolder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.config.SimpleBrokerRegistration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.messaging.SessionConnectEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import org.springframework.web.socket.messaging.StompSubProtocolHandler;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Configuration
@EnableConfigurationProperties(WebSocketProperties.class)
@RequiredArgsConstructor
@Slf4j
@ConditionalOnProperty(prefix = WebSocketProperties.PREFIX, name = "enabled", havingValue = "true")
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private final WebSocketProperties webSocketProperties;

    private final RedisTemplate<String, Object> redisTemplate;

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
    @Bean
    public TaskExecutor brokerChannelExecutor(){
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(webSocketProperties.getTask().getPoolSize());
        taskExecutor.setMaxPoolSize(webSocketProperties.getTask().getMaxPoolSize());
        taskExecutor.setDaemon(webSocketProperties.getTask().isDaemon());
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public void registerStompEndpoints(@NonNull StompEndpointRegistry registry) {
        //注册一个Stomp的节点（endpoint）,并指定使用SockJS协议。
        registry.addEndpoint(webSocketProperties.getEndpoint())
                .setAllowedOriginPatterns("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(@NonNull MessageBrokerRegistry registry) {
        //服务端发送消息给客户端的域,多个用逗号隔开
        SimpleBrokerRegistration simpleBrokerRegistration = registry.enableSimpleBroker(webSocketProperties.getBrokerPaths().toArray(new String[0]));
        //服务心跳
        long heartBeatIn = webSocketProperties.getHeartBeatIn();
        long heartBeatOut = webSocketProperties.getHeartBeatOut();
        if (heartBeatIn > 0 && heartBeatOut > 0) {
            long[] heartBeatValue = new long[]{heartBeatIn, heartBeatOut};
            simpleBrokerRegistration.setHeartbeatValue(heartBeatValue);
        }
        if (webSocketProperties.getTask().isEnabled()) {
            simpleBrokerRegistration.setTaskScheduler(createTaskScheduler());
        }
        //定义一对一推送的时候前缀
        registry.setUserDestinationPrefix(webSocketProperties.getUserDestinationPrefix());
        //配置一个或多个前缀，通过前缀过滤出需要被注解方法处理的消息，例如：前缀为/app的destination可以通过@MessageMapping方法
        //注解处理，而其他destination则直接交给broker处理;例如前缀为/app,则订阅通道/app/message的@MessageMapping("/message")即可接收到消息
        registry.setApplicationDestinationPrefixes(webSocketProperties.getApplicationDestinationPrefixes().toArray(new String[0]));
    }

    /**
     * 创建线程池
     */
    private TaskScheduler createTaskScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(webSocketProperties.getTask().getPoolSize());
        threadPoolTaskScheduler.setDaemon(webSocketProperties.getTask().isDaemon());
        threadPoolTaskScheduler.setRemoveOnCancelPolicy(webSocketProperties.getTask().isRemoveOnCancelPolicy());
        threadPoolTaskScheduler.setThreadNamePrefix(webSocketProperties.getTask().getThreadNamePrefix());
        threadPoolTaskScheduler.setBeanName(webSocketProperties.getTask().getBeanName());
        threadPoolTaskScheduler.initialize();
        return threadPoolTaskScheduler;
    }


    /**
     * 用于传输从webSocket客户端接收的消息
     */
    @Override
    public void configureClientInboundChannel(@NonNull ChannelRegistration registration) {
        registration.interceptors(new WebSocketClientInboundChannel(redisTemplate));
    }

    /**
     * 用于传输向webSocket客户端发送的消息
     */
    @Override
    public void configureClientOutboundChannel(@NonNull ChannelRegistration registration) {
        registration.interceptors(new WebSocketClientOutboundChannel());
    }

    /**
     * 连接事件监听
     */
    @EventListener
    public void handlerWebSocketConnectListener(SessionConnectEvent event) {
        String id = "";
        if (event.getSource() instanceof WebSocketSession) {
            id = ((WebSocketSession) event.getSource()).getId();
        }
        if (event.getSource() instanceof StompSubProtocolHandler) {
            id = (String) event.getMessage().getHeaders().get(SESSION_ID_HEADER);
        }
        log.info("用户[{}]创建连接:{}", WebSocketHolder.getUserName(event), id);
    }

    /**
     * 断开连接事件监听
     */
    @EventListener
    public void handlerWebSocketDisconnectListener(SessionDisconnectEvent event) {
        log.info("用户{}断开连接:{}", WebSocketHolder.getUserName(event), event.getSessionId());
    }
}
